﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using Glissando.Properties;
using JoshSmith;
using Sanford.Multimedia.Midi;
using InputDevice = Sanford.Multimedia.Midi.InputDevice;

namespace Glissando
{
    // http://en.wikipedia.org/wiki/Key_(music)
    // http://en.wikipedia.org/wiki/Note

    // todo: divide in many smaller view models...

    internal class MainViewModel : INotifyPropertyChanged, IJamService
    {
        #region Loading/playing properties

        private const string NoFileLoaded = "no MIDI file loaded";

        private bool _isLoading;
        private bool _IsPlaying;

        private int _loadingProgress;
        private string _Title = NoFileLoaded;

        public bool CanPlay
        {
            get { return IsMidiFileLoaded && !IsPlaying; }
        }

        public bool IsLoading
        {
            get { return _isLoading; }
            set
            {
                _isLoading = value;
                RaisePropertyChanged("IsLoading");
            }
        }

        public int LoadingProgress
        {
            get { return _loadingProgress; }
            set
            {
                _loadingProgress = value;
                RaisePropertyChanged("LoadingProgress");
            }
        }

        public bool IsMidiFileLoaded
        {
            get { return sequence1 != null ? sequence1.GetLength() > 0 : false; }
        }

        public bool IsPlaying
        {
            get { return _IsPlaying; }
            set
            {
                _IsPlaying = value;
                RaisePropertyChanged("IsPlaying");
                RaisePropertyChanged("CanPlay");
            }
        }

        public int SequencePosition
        {
            get { return sequencer1.Position; }
            set { sequencer1.Position = value; }
        }

        public int SequenceLength
        {
            get { return sequence1.GetLength(); }
        }

        public string Time
        {
            get
            {
                // TODO - not correct when tempo changes?
                double bpm = 60000000 / sequencer1.Clock.Tempo;
                double secondsPerTick = 120.0 / sequence1.Division / bpm / 2;
                var totalSeconds = (int)(SequencePosition * secondsPerTick);
                int minutes = totalSeconds / 60;
                int seconds = totalSeconds - minutes * 60;
                return String.Format("{0}:{1:00} @ {2:0} bpm", minutes, seconds, bpm);
            }
        }

        public string Title
        {
            get { return _Title; }
            set
            {
                _Title = value;
                RaisePropertyChanged("Title");
            }
        }

        #endregion

        private PitchTest _pitchTest = new PitchTest();
        private bool _Stereo = true;


        private InputDevice inDevice = null;
        private bool[] isPianoChannel = new bool[128];
        private OutputDevice outDevice;
        private Window OwnerWindow;
        private OutputDevice pianoOutDevice;
        private PianoModel3D pianoView;
        private PianoModel3D pianoView2;
        private DispatcherTimer pitchTestTimer;
        private Sequence sequence1 = null;
        private SequencerModified sequencer1 = null;
        public double StereoBase = 10; // mm

        private bool[] mutedChannels;

        public bool Channel01 { get { return !IsMuted(0); } set { Mute(0, !value); } }
        public bool Channel02 { get { return !IsMuted(1); } set { Mute(1, !value); } }

        private bool IsMuted(int p)
        {
            if (p < mutedChannels.Length)
                return mutedChannels[p];
            return false;
        }

        private void Mute(int p, bool mute)
        {
            if (p < mutedChannels.Length)
                mutedChannels[p] = mute;
            ReleaseAllKeys();
        }

        public MainViewModel(Window ownerWindow, PianoModel3D pianoView, PianoModel3D pianoRight)
        {
            mutedChannels = new bool[2];

            JamBuddies = new ObservableCollection<JamBuddy>();

            UpdateDeviceLists();
            this.pianoView = pianoView;
            pianoView2 = pianoRight;
            OwnerWindow = ownerWindow;
            for (int i = 0; i < 128; i++)
                isPianoChannel[i] = true;

            ConnectDevices();
            InitializeSequencer();
            RaisePropertyChanged("JamBuddies");
        }

        public string Alias
        {
            get { return Settings.Default.Alias; }
            set { Settings.Default.Alias = value; }
        }

        public string Location
        {
            get { return Settings.Default.Location; }
            set { Settings.Default.Location = value; }
        }

        public string JamRoom
        {
            get { return Settings.Default.JamRoom; }
            set { Settings.Default.JamRoom = value; }
        }

        public double VolumeEqualizerFactor
        {
            get { return Settings.Default.VolumeEqualizerFactor; }
            set { Settings.Default.VolumeEqualizerFactor = value; }
        }
        public double PlaybackVolume
        {
            get { return Settings.Default.PlaybackVolume; }
            set { Settings.Default.PlaybackVolume = value; }
        }

        public bool IsClosing { get; set; }

        public Settings Settings
        {
            get { return Settings.Default; }
        }

        public bool PlayPianoOnly
        {
            get { return Settings.Default.PlayPianoOnly; }
            set { Settings.Default.PlayPianoOnly = value; }
        }

        // MIDI device objects

        public PitchTest PitchTest
        {
            get { return _pitchTest; }
        }

        public bool Stereo
        {
            get { return _Stereo; }
            set
            {
                _Stereo = value;
                RaisePropertyChanged("Stereo");
            }
        }

        #region Looping

        private int _LoopEndPosition = 0;
        private int _LoopStartPosition = 0;

        public int LoopStartPosition
        {
            get { return _LoopStartPosition; }
            set
            {
                _LoopStartPosition = value;
                RaisePropertyChanged("LoopStartPosition");
            }
        }

        public int LoopEndPosition
        {
            get { return _LoopEndPosition; }
            set
            {
                _LoopEndPosition = value;
                RaisePropertyChanged("LoopEndPosition");
            }
        }

        public bool IsLooping { get; set; }

        #endregion

        #region MIDI devices

        public List<string> InputDevices { get; private set; }
        public List<string> OutputDevices { get; private set; }

        public int InputDeviceID
        {
            get { return Settings.Default.InputDeviceID; }
            set { Settings.Default.InputDeviceID = value; }
        }

        public int OutputDeviceID
        {
            get { return Settings.Default.OutputDeviceID; }
            set { Settings.Default.OutputDeviceID = value; }
        }

        public int PianoOutputDeviceID
        {
            get { return Settings.Default.PianoOutputDeviceID; }
            set { Settings.Default.PianoOutputDeviceID = value; }
        }

        #endregion

        #region Jam

        private RelayCommand _JamEnterCommand;

        private RelayCommand _JamLeaveCommand;
        private JamPeer peer = null;
        public Dictionary<Guid, JamBuddy> PeerLookup = new Dictionary<Guid, JamBuddy>();
        private Thread peerThread = null;

        public RelayCommand JamEnterCommand
        {
            get
            {
                if (_JamEnterCommand == null)
                    _JamEnterCommand = new RelayCommand(p => EnterJam(), p => CanEnterJam());
                return _JamEnterCommand;
            }
        }

        public RelayCommand JamLeaveCommand
        {
            get
            {
                if (_JamLeaveCommand == null)
                    return _JamLeaveCommand = new RelayCommand(p => LeaveJam(), p => CanLeaveJam());
                return _JamLeaveCommand;
            }
        }

        public string JamStatus { get; set; }
        public ObservableCollection<JamBuddy> JamBuddies { get; private set; }

        public void Say(string sender, string message)
        {
            Debug.WriteLine(sender + ": " + message);
        }

        public void Play(Guid sender, int cmd, int ch, int data1, int data2)
        {
            if (peer == null)
                return;
            if (sender == peer.Id)
                return;
            var m = new ChannelMessage((ChannelCommand)cmd, ch, data1, data2);
            var e = new ChannelMessageEventArgs(m);
            HandleChannelMessagePlayed(null, e);
        }

        public void Hello(Guid sender, string alias, string location)
        {
            if (peer == null)
                return;
            if (sender == peer.Id) // yourself
                return;
            if (PeerLookup.ContainsKey(sender))
            {
                JamBuddy buddy = PeerLookup[sender];
                buddy.Alias = alias;
                buddy.Location = location;
                Debug.WriteLine("Already knows" + alias);
            }
            else
            {
                var buddy = new JamBuddy(alias, location);
                PeerLookup.Add(sender, buddy);
                OwnerWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action<JamBuddy>(AddBuddy), buddy);
                Debug.WriteLine("Hello from " + alias + ", " + location);

                Debug.WriteLine("Sending hello");
                // send Hello back
                peer.Channel.Hello(peer.Id, Alias, Location);
            }
        }

        public void Goodbye(Guid sender)
        {
            if (PeerLookup.ContainsKey(sender))
            {
                JamBuddy p = PeerLookup[sender];
                Debug.WriteLine("Goodbye from " + p);
                PeerLookup.Remove(sender);
                JamBuddies.Remove(p);
                return;
            }
            Debug.WriteLine("Goodbye from unknown...");
        }

        private bool CanLeaveJam()
        {
            return peer != null;
        }

        private void LeaveJam()
        {
            if (peer.Channel != null)
                peer.Channel.Goodbye(peer.Id);
            peer.Stop();
            peerThread.Join();
            peer = null;
            peerThread = null;

            JamBuddies.Clear();
            PeerLookup.Clear();

            JamStatus = "Disconnected.";
            RaisePropertyChanged("JamStatus");

            RaisePropertyChanged("JamRoom");
        }

        private bool CanEnterJam()
        {
            return peer == null;
        }

        private void EnterJam()
        {
            JamStatus = String.Format("Connecting to {0}.", JamRoom);
            RaisePropertyChanged("JamStatus");

            peer = new JamPeer(Guid.NewGuid(), JamRoom, this);
            peer.Connected += peer_Connected;
            peerThread = new Thread(peer.Run) { IsBackground = true };
            peerThread.Start();
        }

        private void peer_Connected()
        {
            JamStatus = String.Format("Connected to {0}.", JamRoom);
            Debug.WriteLine(JamStatus);
            RaisePropertyChanged("JamStatus");
            Debug.WriteLine("Sending hello");
            peer.Channel.Hello(peer.Id, Alias, Location);
        }

        public void Jam(ChannelMessage msg)
        {
            if (peer != null && peer.IsConnected)
                peer.Channel.Play(peer.Id, (int)msg.Command, msg.MidiChannel, msg.Data1, msg.Data2);
        }

        private void AddBuddy(JamBuddy obj)
        {
            JamBuddies.Add(obj);
        }

        #endregion

        #region Pitch test properties

        private bool _IsFullScreen;
        private bool _IsPitchTestRunningTest;
        private string _NoteName;
        private string _PlayTitle;
        private string _TestResult;
        private bool _TransparentBackground;

        public bool IsPitchTestRunning
        {
            get { return _IsPitchTestRunningTest; }
            set
            {
                _IsPitchTestRunningTest = value;
                RaisePropertyChanged("IsPitchTestRunning");
                StartOrStopPitchTest();
            }
        }

        public int PitchTestTime
        {
            get { return Settings.Default.PitchTestTime; }
            set { Settings.Default.PitchTestTime = value; }
        }

        //        public List<string> Keys { get; private set; }

        public bool ColoredKeys
        {
            get { return Settings.ColoredKeys; }
            set
            {
                Settings.ColoredKeys = value;
                RaisePropertyChanged("ColoredKeys");
            }
        }

        public string PlayTitle
        {
            get { return _PlayTitle; }
            set
            {
                _PlayTitle = value;
                RaisePropertyChanged("PlayTitle");
            }
        }

        public string TestResult
        {
            get { return _TestResult; }
            set
            {
                _TestResult = value;
                RaisePropertyChanged("TestResult");
            }
        }

        public string NoteName
        {
            get { return _NoteName; }
            set
            {
                _NoteName = value;
                RaisePropertyChanged("NoteName");
            }
        }

        public bool IsFullScreen
        {
            get { return _IsFullScreen; }
            set
            {
                _IsFullScreen = value;
                RaisePropertyChanged("IsFullScreen");
            }
        }

        public bool TransparentBackground
        {
            get { return _TransparentBackground; }
            set
            {
                _TransparentBackground = value;
                RaisePropertyChanged("TransparentBackground");
            }
        }

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        private void UpdateDeviceLists()
        {
            InputDevices = new List<string>();
            for (int i = 0; i < InputDevice.DeviceCount; i++)
            {
                MidiInCaps caps = InputDevice.GetDeviceCapabilities(i);
                InputDevices.Add(caps.name);
            }

            OutputDevices = new List<string>();
            for (int i = 0; i < OutputDevice.DeviceCount; i++)
            {
                MidiOutCaps caps = OutputDevice.GetDeviceCapabilities(i);
                OutputDevices.Add(caps.name);
            }
        }

        public void ReconnectDevices()
        {
            CloseDevices();
            ConnectDevices();
        }

        private void ConnectDevices()
        {
            if (OutputDevice.DeviceCount == 0)
            {
                return;
            }

            try
            {
                if (InputDeviceID < InputDevice.DeviceCount)
                {
                    inDevice = new InputDevice(InputDeviceID);
                    inDevice.ChannelMessageReceived += inDevice_ChannelMessageReceived;
                    inDevice.StartRecording();
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }

            try
            {
                if (OutputDeviceID < OutputDevice.DeviceCount)
                {
                    outDevice = new OutputDevice(OutputDeviceID);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }

            try
            {
                if (PianoOutputDeviceID < OutputDevice.DeviceCount)
                {
                    if (PianoOutputDeviceID == OutputDeviceID)
                        pianoOutDevice = outDevice;
                    else
                        pianoOutDevice = new OutputDevice(PianoOutputDeviceID);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
        }

        private void InitializeSequencer()
        {
            sequence1 = new Sequence();
            sequencer1 = new SequencerModified();
            sequence1.Format = 1;
            sequencer1.Position = 0;
            sequencer1.Sequence = sequence1;
            sequencer1.PlayingCompleted += new EventHandler(HandlePlayingCompleted);
            sequencer1.ChannelMessagePlayed += new EventHandler<ChannelMessageEventArgs>(HandleChannelMessagePlayed);
            sequencer1.Stopped += new EventHandler<StoppedEventArgs>(HandleStopped);
            // sequencer1.SysExMessagePlayed += new System.EventHandler<Sanford.Multimedia.Midi.SysExMessageEventArgs>(HandleSysExMessagePlayed);
            // sequencer1.Chased += new System.EventHandler<Sanford.Multimedia.Midi.ChasedEventArgs>(HandleChased);
            sequence1.LoadProgressChanged += new ProgressChangedEventHandler(sequence1_LoadProgressChanged);
            sequence1.LoadCompleted += HandleLoadCompleted;
        }

        public void Close()
        {
            sequence1.Dispose();
            //   sequencer1.Dispose();
            CloseDevices();
            Settings.Default.Save();
        }

        protected void CloseDevices()
        {
            if (inDevice != null)
                inDevice.Close();
            if (outDevice != null)
                outDevice.Close();
            if (pianoOutDevice != null)
                pianoOutDevice.Close();
            inDevice = null;
            outDevice = null;
            pianoOutDevice = null;
        }

        protected void RaisePropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }

        #region Pitch test operations

        private void StartOrStopPitchTest()
        {
            if (IsPitchTestRunning)
            {
                UpdatePitchTestSettings();

                pitchTestTimer = new DispatcherTimer();
                pitchTestTimer.Interval = TimeSpan.FromSeconds(0.1);
                pitchTestTimer.Tick += new EventHandler(pitchTestTimer_Tick);
                pitchTestTimer.Start();
                _pitchTest.Start();
            }
            else
            {
                ReleaseTestChord();
                pitchTestTimer.Stop();
                pitchTestTimer = null;
            }
            TestResult = "";
            NoteName = "";
        }

        private void ReleaseTestChord()
        {
            if (_pitchTest.TestChord != null)
                foreach (int note in _pitchTest.TestChord)
                    ReleaseKey(note);
        }

        private void PlayTestChord()
        {
            if (_pitchTest.TestChord != null)
                foreach (int note in _pitchTest.TestChord)
                    PlayKey(note, false);
        }

        private void pitchTestTimer_Tick(object sender, EventArgs e)
        {
            if (!_pitchTest.CorrectChordPressed && String.IsNullOrEmpty(NoteName) && _pitchTest.TestChord != null)
            {
                NoteName = _pitchTest.TestChordName;
                pitchTestTimer.Interval = TimeSpan.FromSeconds(1);
                pitchTestTimer.Start();
                return;
            }
            else
                NoteName = "";

            ReleaseTestChord();
            _pitchTest.FindNewTestChord();
            PlayTestChord();

            pitchTestTimer.Interval = TimeSpan.FromSeconds(PitchTestTime);
            pitchTestTimer.Start();
            TestResult = _pitchTest.TestResult;
        }

        public void PitchTestCheckInput()
        {
            if (!IsPitchTestRunning) return;
            if (_pitchTest.Check())
            {
                pitchTestTimer.Interval = TimeSpan.FromSeconds(1);
                pitchTestTimer.Start();
                NoteName = _pitchTest.TestChordName;
            }
            TestResult = _pitchTest.TestResult;
        }

        public void UpdatePitchTestSettings()
        {
            PitchTest.TestScales.Clear();
            if (Settings.TestC) PitchTest.TestScales.Add(Scale.C);
            if (Settings.TestCsharp) PitchTest.TestScales.Add(Scale.Csharp);
            if (Settings.TestD) PitchTest.TestScales.Add(Scale.D);
            if (Settings.TestDsharp) PitchTest.TestScales.Add(Scale.Dsharp);
            if (Settings.TestE) PitchTest.TestScales.Add(Scale.E);
            if (Settings.TestF) PitchTest.TestScales.Add(Scale.F);
            if (Settings.TestFsharp) PitchTest.TestScales.Add(Scale.Fsharp);
            if (Settings.TestG) PitchTest.TestScales.Add(Scale.G);
            if (Settings.TestGsharp) PitchTest.TestScales.Add(Scale.Gsharp);
            if (Settings.TestA) PitchTest.TestScales.Add(Scale.A);
            if (Settings.TestAsharp) PitchTest.TestScales.Add(Scale.Asharp);
            if (Settings.TestB) PitchTest.TestScales.Add(Scale.B);

            PitchTest.TestScaleModifiers.Clear();
            if (Settings.TestMajorScales) PitchTest.TestScaleModifiers.Add(ScaleModifier.Major);
            if (Settings.TestMinorScales) PitchTest.TestScaleModifiers.Add(ScaleModifier.Minor);
            if (Settings.TestBluesScales) PitchTest.TestScaleModifiers.Add(ScaleModifier.Blues);

            PitchTest.TestChords.Clear();
            if (Settings.TestSingleNotes) PitchTest.TestChords.Add(Chord.SingleNote);
            if (Settings.TestMinorSecond) PitchTest.TestChords.Add(Chord.MinorSecond);
            if (Settings.TestMajorSecond) PitchTest.TestChords.Add(Chord.MajorSecond);
            if (Settings.TestMinorThird) PitchTest.TestChords.Add(Chord.MinorThird);
            if (Settings.TestMajorThird) PitchTest.TestChords.Add(Chord.MajorThird);
            if (Settings.TestPerfectFourth) PitchTest.TestChords.Add(Chord.PerfectFourth);
            if (Settings.TestDiminishedFifth) PitchTest.TestChords.Add(Chord.DiminishedFifth);
            if (Settings.TestPerfectFifth) PitchTest.TestChords.Add(Chord.PerfectFifth);
            if (Settings.TestMinorSixth) PitchTest.TestChords.Add(Chord.MinorSixth);
            if (Settings.TestMajorSixth) PitchTest.TestChords.Add(Chord.MajorSixth);
            if (Settings.TestMinorSeventh) PitchTest.TestChords.Add(Chord.MinorSeventh);
            if (Settings.TestMajorSeventh) PitchTest.TestChords.Add(Chord.MajorSeventh);
            if (Settings.TestPerfectOctave) PitchTest.TestChords.Add(Chord.PerfectOctave);
            if (Settings.TestMinorTriad) PitchTest.TestChords.Add(Chord.MinorTriad);
            if (Settings.TestMajorTriad) PitchTest.TestChords.Add(Chord.MajorTriad);
            if (Settings.TestAugmentedTriad) PitchTest.TestChords.Add(Chord.AugmentedTriad);
            if (Settings.TestDiminishedTriad) PitchTest.TestChords.Add(Chord.DiminishedTriad);
            if (Settings.TestDiminishedSeventh) PitchTest.TestChords.Add(Chord.DiminishedSeventh);
            if (Settings.TestMinorSeventhChord) PitchTest.TestChords.Add(Chord.MinorSeventhChord);
            PitchTest.MinOctave = Settings.TestMinOctave;
            PitchTest.MaxOctave = Settings.TestMaxOctave;
        }

        #endregion

        #region Sequencer operations

        public void Stop()
        {
            if (outDevice != null)
            {
                lock (outDevice)
                {
                    LoopEndPosition = -1;
                    sequencer1.Stop();
                    sequencer1.Position = 0;
                }
            }
            PlayTitle = null;

            IsPlaying = false;
            RaisePropertyChanged("SequencePosition");
            RaisePropertyChanged("Time");
            RaisePropertyChanged("IsPlaying");
            RaisePropertyChanged("CanPlay");

            RaiseRequerySuggested();
        }

        private void RaiseRequerySuggested()
        {
            // Raise the CanExecute events (on dispatcher thread!)
            OwnerWindow.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(RequerySuggested));
        }

        private void RequerySuggested()
        {
            CommandManager.InvalidateRequerySuggested();
        }

        internal void Start()
        {
            sequencer1.Continue();
            IsPlaying = true;
            PlayTitle = Title;
        }

        internal void Pause()
        {
            sequencer1.Stop();
            IsPlaying = false;
        }

        internal void ReduceSpeed()
        {
            sequencer1.Clock.SpeedFactor *= 1.1;
        }

        internal void NormalSpeed()
        {
            sequencer1.Clock.SpeedFactor = 1.0;
        }

        internal void Open(string path)
        {
            if (outDevice != null)
            {
                lock (outDevice)
                {
                    IsLoading = false;
                    Title = Path.GetFileNameWithoutExtension(path);
                    sequencer1.Stop();
                }
            }

            Thread.Sleep(200);
            if (sequence1 == null)
                sequence1 = new Sequence();
            sequence1.Clear();
            try
            {
                IsLoading = true;
                sequence1.LoadAsync(path);
            }
            catch
            {
                sequence1 = null;
                Title = NoFileLoaded;
            }
            LoopStartPosition = 0;
            LoopEndPosition = 0;
            RaisePropertyChanged("IsMidiFileLoaded");
            RaisePropertyChanged("CanPlay");
        }


        public void SetLoopStart()
        {
            LoopStartPosition = sequencer1.Position;
        }

        public void SetLoopEnd()
        {
            LoopEndPosition = sequencer1.Position;
        }

        #endregion

        #region MIDI message handlers

        private void inDevice_ChannelMessageReceived(object sender, ChannelMessageEventArgs e)
        {
            Jam(e.Message);
            if (e.Message.Command == ChannelCommand.NoteOn)
            {
                if (e.Message.Data2 > 0)
                {
                    PressViewKey(1, e.Message.Data1, e.Message.Data2);
                    PressTestKey(e.Message.Data1);
                    PitchTestCheckInput();
                }
                else
                {
                    ReleaseTestKey(e.Message.Data1, false);
                    ReleaseViewKey(e.Message.Data1);
                }
            }
            if (e.Message.Command == ChannelCommand.NoteOff)
            {
                ReleaseTestKey(e.Message.Data1, false);
                ReleaseViewKey(e.Message.Data1);
            }
        }

        public void PressViewKey(int ch, int data1, int data2)
        {
            pianoView.PressKey(ch, data1, data2);
            if (pianoView2 != null)
            {
                // not 100% synchronized, but good enough for now...
                pianoView2.PressKey(ch, data1, data2);
            }
        }

        public void ReleaseViewKey(int data1)
        {
            pianoView.ReleaseKey(data1);
            if (pianoView2 != null)
            {
                pianoView2.ReleaseKey(data1);
            }
        }

        public void PressTestKey(int p)
        {
            _pitchTest.PressKey(p);
        }

        public void ReleaseTestKey(int p, bool dontForget)
        {
            _pitchTest.ReleaseKey(p, dontForget);
        }

        public void Play(int note, bool testInput)
        {
            if (note > 0)
            {
                PlayKey(note, true);
                if (testInput)
                {
                    PressTestKey(note);
                    PitchTestCheckInput();
                }
            }
        }

        public void Release(int currentKeyNumber, bool testInput)
        {
            ReleaseKey(currentKeyNumber);
            ReleaseTestKey(currentKeyNumber, true);
        }

        public void PlayKey(int note, bool show)
        {
            int force = 60;
            var m = new ChannelMessage(ChannelCommand.NoteOn, 1, note, force);

            Jam(m);

            if (pianoOutDevice != null)
                pianoOutDevice.Send(m);
            if (show)
                PressViewKey(1, note, force);
        }

        public void ReleaseKey(int note)
        {
            var m = new ChannelMessage(ChannelCommand.NoteOff, 1, note);
            Jam(m);
            if (pianoOutDevice != null)
                pianoOutDevice.Send(m);
            ReleaseViewKey(note);
        }

        public void ReleaseAllKeys()
        {
            for (int i = 21; i <= 108; i++)
                ReleaseKey(i);
        }

        private void HandleChannelMessagePlayed(object sender, ChannelMessageEventArgs e)
        {
            if (IsClosing) return;
            if (outDevice==null) return;
            lock (outDevice)
            {
                if (IsMuted(e.Message))
                    return;
                bool piano = IsPianoMessage(e.Message);
                if (piano)
                {
                    ChannelMessage msg = e.Message;
                    if (msg.Command == ChannelCommand.NoteOn)
                    {
                        if (msg.Data2 > 0)
                        {
                            int data2 = GetPlaybackVolume(e.Message.Data2);
                            msg = new ChannelMessage(msg.Command, msg.MidiChannel, msg.Data1, data2);
                        }
                    }
                    if (pianoOutDevice != null)
                        pianoOutDevice.Send(msg);

                    // piano view must be updated on UI thread
                    InvokeNotePlayed(msg);
                }
                else if (outDevice != null)
                    outDevice.Send(e.Message);
            }
        }

        private bool IsMuted(ChannelMessage cmsg)
        {
            return IsMuted(cmsg.MidiChannel);
        }

        private void InvokeNotePlayed(ChannelMessage m)
        {
            OwnerWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action<ChannelMessage>(NotePlayed), m);
        }

        private bool IsPianoMessage(ChannelMessage m)
        {
            if (m.Command == ChannelCommand.ProgramChange)
            {
                SetChannelInstrument(m.Data1, m.Data2);
            }
            if (m.MidiChannel == 9) // Drum channel
                return false;
            if (!IsPiano(m.MidiChannel))
                return false;
            return true;
        }

        public void NotePlayed(ChannelMessage m)
        {
            if (m.Command == ChannelCommand.NoteOn)
            {
                if (m.Data2 > 0)
                {
                    PressViewKey(m.MidiChannel, m.Data1, m.Data2);
                }
                else
                    ReleaseViewKey(m.Data1);
            }
            if (m.Command == ChannelCommand.NoteOff)
            {
                ReleaseViewKey(m.Data1);
            }
        }

        private int GetPlaybackVolume(int p)
        {
            int m = 100;
            double vol=m + (p - m) * VolumeEqualizerFactor * 0.01;
            vol *= PlaybackVolume*0.01;
            return (int) vol;
        }

        public void CheckLoop()
        {
            RaisePropertyChanged("SequencePosition");
            RaisePropertyChanged("Time");

            if (outDevice == null)
                return;

            lock (outDevice)
            {
                if (!IsLooping || LoopEndPosition <= 0) return;
                if (SequencePosition > LoopEndPosition)
                {
                    SequencePosition = LoopStartPosition;
                }
            }
        }

        private bool IsPiano(int p)
        {
            return true;
        }

        public void SetChannelInstrument(int data1, int data2)
        {
            if (data1 >= 0 && data1 < 128)
            {
                isPianoChannel[data1] = data2 < 8;
            }
        }

        public void SetInstrument(int channel, int instrument)
        {
            var m = new ChannelMessage(ChannelCommand.ProgramChange, channel, instrument);
            bool piano = IsPianoMessage(m);
            if (piano)
                pianoOutDevice.Send(m);
            else
                outDevice.Send(m);
        }

        private void HandleChased(object sender, ChasedEventArgs e)
        {
            foreach (ChannelMessage m in e.Messages)
            {
                bool piano = IsPianoMessage(m);
                if (piano)
                    pianoOutDevice.Send(m);
                else
                    outDevice.Send(m);
            }
        }

        private void HandleSysExMessagePlayed(object sender, SysExMessageEventArgs e)
        {
            // outDevice.Send(e.Message); // Sometimes causes an exception to be thrown because the output device is overloaded.
        }

        private void HandleStopped(object sender, StoppedEventArgs e)
        {
            foreach (ChannelMessage m in e.Messages)
            {
                bool piano = IsPianoMessage(m);
                if (piano)
                    pianoOutDevice.Send(m);
                else
                    outDevice.Send(m);
                InvokeNotePlayed(m);
            }

            if (SequencePosition >= SequenceLength)
                Stop();
        }

        private void HandlePlayingCompleted(object sender, EventArgs e)
        {
            Stop();
        }

        private void HandleLoadCompleted(object sender, AsyncCompletedEventArgs e)
        {
            IsLoading = false;
            // todo: is this a good idea?
            RaisePropertyChanged("SequenceLength");

            RaisePropertyChanged("IsMidiFileLoaded");
            RaisePropertyChanged("CanPlay");

            RaiseRequerySuggested();
        }

        private void sequence1_LoadProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            LoadingProgress = e.ProgressPercentage;
        }

        #endregion
    }
}